﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MMessaging.Address;
using MMessaging.Address.Services;
using MMessaging.MessagingEx.Common;

namespace MMessaging.Test.Fahrstuhl.Engine
{
	public class ElevatorService : MAddressServiceBase
	{
		#region Types
		#endregion
		
		#region fields

		private List<ButtonStateBase> _buttonStates = new List<ButtonStateBase>();
		private FloorEnum _currentFloor;
		private MovementState _currentMovement;
		private UpDownEnum _strategicDirection;
		private double _currentProgress;

		#endregion

		#region private properties

		private byte CurrentProgress
		{
			get { return Convert.ToByte(Math.Floor(_currentProgress)); }
		}

		private IEnumerable<ButtonStateInsideCabinWantFloor> InsideCabinWantFloorButtonStates
		{
			get
			{
				return _buttonStates.Where(b => b is ButtonStateInsideCabinWantFloor).Cast<ButtonStateInsideCabinWantFloor>();
			}
		}


		private ButtonStateInsideCabinEmergencyDoor InsideCabinEmergencyButtonState
		{
			get
			{
				return _buttonStates.First(b => b is ButtonStateInsideCabinEmergencyDoor) as ButtonStateInsideCabinEmergencyDoor;
			}
		}

		private ButtonStateInsideCabinOpenDoor InsideCabinOpenDoorButtonState
		{
			get
			{
				return _buttonStates.First(b => b is ButtonStateInsideCabinOpenDoor) as ButtonStateInsideCabinOpenDoor;
			}
		}

		private IEnumerable<ButtonStateOutsideCabin> OutsideCabinButtonsStates
		{
			get
			{
				return _buttonStates.Where(b => b is ButtonStateOutsideCabin).Cast<ButtonStateOutsideCabin>();
			}
		}
		#endregion

		#region constructor

		/// <summary>
		/// 
		/// </summary>
		/// <param name="serviceAddress"></param>
		/// <param name="intervallTimeAlifeMessage">
		/// Intervallzeit, für das zyklische Senden der Alife-Message
		/// </param>
		/// <param name="roles"></param>
		public ElevatorService(
			MAddress serviceAddress,
			int intervallTimeAlifeMessage,
			params MAddress[] roles)
			: base(
				serviceAddress,
				intervallTimeAlifeMessage, // time-intervall for alife-message
				// roles of this service
				roles)
		{
			foreach (var location in Enum.GetValues(typeof(ButtonLocationEnum)).Cast<ButtonLocationEnum>())
			{
				switch (location)
				{
					case ButtonLocationEnum.Floor_0:
					case ButtonLocationEnum.Floor_1:
					case ButtonLocationEnum.Floor_2:
						_buttonStates.Add(
							new ButtonStateOutsideCabin(
								floorNo: (int)location,
								pressed: false,
								upDown: UpDownEnum.Up
								));
						_buttonStates.Add(
							new ButtonStateOutsideCabin(
								floorNo: (int)location,
								pressed: false,
								upDown: UpDownEnum.Down
								));
						break;
					case ButtonLocationEnum.InsideCabin:
						foreach (var floorLocation in Enum.GetValues(typeof(FloorEnum)).Cast<FloorEnum>())
						{
							_buttonStates.Add(
								new ButtonStateInsideCabinWantFloor(
									toFloorNo: floorLocation,
									pressed: false));
						}
						_buttonStates.Add(new ButtonStateInsideCabinOpenDoor(pressed: false));
						_buttonStates.Add(new ButtonStateInsideCabinEmergencyDoor(pressed: false));
						break;
					default:
						throw new ArgumentOutOfRangeException();
				}
			}
			_strategicDirection = UpDownEnum.Up;
			_currentFloor = FloorEnum.Floor_0;
			_currentMovement = MovementState.Stopped;
			_currentProgress = 0;

		}

		#endregion

		private void SendCurrentStatusToEverybody()
		{
			this.PostMessage(
				new ElevatorStatusMessage(
					receiver: MDefaultAddress.BROADCAST,
					sender: this.ServiceAddress,
					sendTime: DateTime.Now,
					recentFloor:_currentFloor,
					direction: _currentMovement,
					progress: CurrentProgress));
			_buttonStates.ForEach(state=>
				this.PostMessage(
					new ButtonStatusMessage(
						receiver:MDefaultAddress.BROADCAST,
						sender:this.ServiceAddress,
						sendTime:DateTime.Now,
						buttonState:state.Copy())));
		}

		protected override void OnServiceStarted(IMTask<MAddressMessageBase> task)
		{
			base.OnServiceStarted(task);
			const int INTERVALLTIME = 200;
			const int TIMEFORONEFLOOR = 3000;
			const int TIMEWAITINGCLOSEDOOR = 2000;

			// do till MWaitException (Reason CANCEL) occurs
			while (true)
			{
				task.WaitOnMessageWithTimeoutException(
					logText: GetType().Name + ".WaitOnAnySignal()",
					timeoutMilliseconds: INTERVALLTIME,
					condition: msg => msg is ButtonStatusMessage);

				switch (_currentMovement)
				{
					case MovementState.Stopped:
						switch (_strategicDirection)
						{
							case UpDownEnum.Up:
								if (InsideCabinWantFloorButtonStates.Any(b => b.Pressed && b.ToFloor > _currentFloor))
								{
									_strategicDirection = UpDownEnum.Up;
									_currentMovement = MovementState.Up;
								}
								else if ( InsideCabinWantFloorButtonStates.Any(b => b.Pressed && b.ToFloor < _currentFloor))
								{
									_strategicDirection = UpDownEnum.Down;
									_currentMovement = MovementState.Down;
								}
								else if (OutsideCabinButtonsStates.Any(b => b.Pressed && b.FloorNo > (int)_currentFloor))
								{
									_strategicDirection = UpDownEnum.Up;
									_currentMovement = MovementState.Up;
								}
								else if (OutsideCabinButtonsStates.Any(b => b.Pressed && b.FloorNo < (int)_currentFloor))
								{
									_strategicDirection = UpDownEnum.Down;
									_currentMovement = MovementState.Down;
								}
								
								break;
							case UpDownEnum.Down:
								if (this.InsideCabinWantFloorButtonStates.Any(b => b.Pressed && b.ToFloor < _currentFloor))
									_currentMovement = MovementState.Down;
								break;
							default:
								throw new ArgumentOutOfRangeException();
						}
						break;
					case MovementState.Up:
						_currentProgress += 100 * INTERVALLTIME / TIMEFORONEFLOOR;
						if (_currentProgress > 100)
						{
							_currentProgress = 0;
							_currentFloor++;

							var buttonsWantingCurrentfloor = InsideCabinWantFloorButtonStates
								.Where(b => b.Pressed && b.ToFloor == _currentFloor)
								.Cast<ButtonStateBase>()
								.Union(OutsideCabinButtonsStates.Where(
									b => b.Pressed &&
									     b.FloorNo == (int) _currentFloor &&
									     b.UpDown == _strategicDirection));

							// if cabin must halt on current floor
							if (buttonsWantingCurrentfloor.Any())
							{
								_currentMovement = MovementState.Stopped;
								buttonsWantingCurrentfloor.ForEach(b => b.Pressed = false);
								SendCurrentStatusToEverybody();
								// waiting some time
								task.SleepWithException(TIMEWAITINGCLOSEDOOR);
							}
							// if cabin is going on
							else
							{
								// do nothing
							}
						}
						break;
					case MovementState.Down:
						break;
					default:
						throw new ArgumentOutOfRangeException();
				}

				SendCurrentStatusToEverybody();

			}
		}

		protected override void OnMessage(IMTask<MAddressMessageBase> context)
		{
			IfCurrentMessageIsOfType<ButtonStatusMessage>(context, message =>
			{
				var newButtonState = message.ButtonState;
				_buttonStates.RemoveWhere(b => b.IsButton(newButtonState));
				_buttonStates.Add(newButtonState.Copy());
			});
		}
	}
}
